local Class = require('lua.lang.Class')
local Constants = require('lua.vm.compiler.Constants')
local InstructionPointer = require('lua.vm.compiler.InstructionPointer')
local UpvalueDescriptor = require('lua.vm.UpvalueDescriptor')
local IntPointer = require('lua.vm.compiler.IntPointer')
local LuaBoolean = require('lua.vm.LuaBoolean')
local LuaNumber = require('lua.vm.LuaNumber')
local LuaNil = require('lua.vm.LuaNil')
local ExpressionDescriptor = require('lua.vm.compiler.ExpressionDescriptor')

--- @class FunctionState : Constants
--- @field f Prototype
--- @field h table<LuaValue, number>
--- @field prev FunctionState
--- @field ls LexState
--- @field bl BlockControl
--- @field pc number
--- @field lastTarget number
--- @field jpc number
--- @field nk number
--- @field np number
--- @field firstLocal number
--- @field nLocVars number
--- @field nActVar number
--- @field nUps number
--- @field freeReg number
local FunctionState = Class {
	__extends = Constants
}

--- @constructor
--- @return FunctionState
function FunctionState:new()
	--- @type FunctionState
	local o = {
		pc = 0,
		lastTarget = 0,
		nk = 0,
		np = 0,
		firstLocal = 0,
		nLocVars = 0,
		nActVar = 0,
		nUps = 0,
		freeReg = 0
	}
	return setmetatable(o, FunctionState)
end

--- @param e ExpressionDescriptor
--- @return InstructionPointer
function FunctionState:getCodePtr(e)
	return InstructionPointer:new(self.f.code, e.expValue.opcodeInfo)
end

--- @param e ExpressionDescriptor
--- @return number
function FunctionState:getCode(e)
	return self.f.code[e.expValue.opcodeInfo + 1]
end

--- @param o number
--- @param a number
--- @param sBx number
--- @return number
function FunctionState:codeAsBx(o, a, sBx)
	return self:codeABx(o, a, sBx + self.MAXARG_sBx)
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:setMultipleRet(e)
	self:setReturns(e, self.LUA_MULTRET)
end

--- @param ll LabelDescriptor[]
--- @param ll_n number
--- @param label LuaString
--- @return void
function FunctionState:checkRepeated(ll, ll_n, label)
	for i = self.bl.firstLabel, ll_n - 1 do
		local l = ll[i + 1]
		if label:eq_b(l.identifier) then
			self.ls:semError('label \39' .. label:toLString() .. ' already defined on line ' .. l.sourceLine)
		end
	end
end

--- @param v number
--- @param l number
--- @param msg string
--- @return void
function FunctionState:checkLimit(v, l, msg)
	if v > l then
		self:errorLimit(l, msg)
	end
end

--- @param limit number
--- @param what string
--- @return void
function FunctionState:errorLimit(limit, what)
	local msg = self.f.lineDefined == 0 and
			'main function has more than ' .. tostring(limit) .. ' ' .. what or
			'function at line ' .. self.f.lineDefined .. ' has more than ' .. limit .. ' ' .. what
	self.ls:lexError(msg, 0)
end

--- @param i number
--- @return LocalVariable
function FunctionState:getLocVar(i)
	local idx = self.ls.dyd.activeVariables[self.firstLocal + i + 1].index
	self:assert(idx < self.nLocVars)
	return self.f.locVars[idx + 1]
end

--- @param toLevel number
--- @return void
function FunctionState:removeVars(toLevel)
	self.ls.dyd.activeVariableCount = self.ls.dyd.activeVariableCount - (self.nActVar - toLevel)
	while self.nActVar > toLevel do
		local nActVar = self.nActVar - 1
		self.nActVar = nActVar
		self:getLocVar(nActVar).endPc = self.pc
	end
end

--- @param name LuaString
--- @return number
function FunctionState:searchUpvalue(name)
	local up = self.f.upvalues
	for i = 1, self.nUps do
		if up[i].variableName:eq_b(name) then
			return i - 1
		end
	end
	return -1
end

--- @param name LuaString
--- @param v ExpressionDescriptor
--- @return number
function FunctionState:newUpvalue(name, v)
	self:checkLimit(self.nUps + 1, self.LUAI_MAXUPVAL, 'upvalues')
	local nUps = self.nUps + 1
	self.nUps = nUps
	local upvalues = self.f.upvalues
	if upvalues == nil then
		upvalues = {}
		self.f.upvalues = upvalues
	end
	upvalues[nUps] = UpvalueDescriptor:new(name, v.expressionType == self.VLOCAL, v.expValue.opcodeInfo)
	return nUps - 1
end

--- @param n LuaString
--- @return number
function FunctionState:searchVar(n)
	for i = self.nActVar - 1, 0, -1 do
		if n:eq_b(self:getLocVar(i).varName) then
			return i
		end
	end
	return -1
end

--- @param level number
--- @return void
function FunctionState:markUpvalue(level)
	local bl = self.bl
	while bl.nActVar > level do
		bl = bl.previous
	end
	bl.upvalue = true
end

--- @param bl BlockControl
--- @return void
function FunctionState:moveGotosOut(bl)
	local i = bl.firstGoto
	local gl = self.ls.dyd.gotoLabels
	while i < self.ls.dyd.gotoLabelCount do
		local gt = gl[i + 1]
		if gt.activeVariableCount > bl.nActVar then
			if bl.upvalue then
				self:patchCloseUpvalues(gt.pc, bl.nActVar)
			end
			gt.activeVariableCount = bl.nActVar
		end
		if not self.ls:findLabel(i) then
			i = i + 1
		end
	end
end

--- @param bl BlockControl
--- @param isLoop boolean
--- @return void
function FunctionState:enterBlock(bl, isLoop)
	bl.isLoop = isLoop
	bl.nActVar = self.nActVar
	bl.firstLabel = self.ls.dyd.labelCount
	bl.firstGoto = self.ls.dyd.gotoLabelCount
	bl.upvalue = false
	bl.previous = self.bl
	self.bl = bl
	self:assert(self.freeReg == self.nActVar)
end

--- @return void
function FunctionState:leaveBlock()
	local bl = self.bl
	if bl.previous ~= nil and bl.upvalue then
		local j = self:jump()
		self:patchCloseUpvalues(j, bl.nActVar)
		self:patchToHere(j)
	end
	if bl.isLoop then
		self.ls:breakLabel()
	end
	self.bl = bl.previous
	self:removeVars(bl.nActVar)
	self:assert(bl.nActVar == self.nActVar)
	self.freeReg = self.nActVar
	self.ls.dyd.labelCount = bl.firstLabel
	if bl.previous ~= nil then
		self:moveGotosOut(bl)
	elseif bl.firstGoto < self.ls.dyd.gotoLabelCount then
		self.ls:undefGoto(self.ls.dyd.gotoLabels[bl.firstGoto + 1])
	end
end

--- @param cc TableConstructorControl
--- @return void
function FunctionState:closeListField(cc)
	if cc.valueExpression.expressionType == self.VVOID then
		return
	end
	self:exp2NextReg(cc.valueExpression)
	cc.valueExpression.expressionType = self.VVOID
	if cc.pendingStoreCount == self.LFIELDS_PER_FLUSH then
		self:setList(cc.tableExpression.expValue.opcodeInfo, cc.recordElementCount, cc.pendingStoreCount)
		cc.pendingStoreCount = 0
	end
end

--- @param k number
--- @return boolean
function FunctionState:hasMultipleRet(k)
	return k == self.VCALL or k == self.VVARARG
end

--- @param cc TableConstructorControl
--- @return void
function FunctionState:lastListField(cc)
	if cc.pendingStoreCount == 0 then
		return
	end
	if self:hasMultipleRet(cc.valueExpression.expressionType) then
		self:setMultipleRet(cc.valueExpression)
		self:setList(cc.tableExpression.expValue.opcodeInfo, cc.recordElementCount, self.LUA_MULTRET)
		cc.recordElementCount = cc.recordElementCount - 1
	else
		if cc.valueExpression.expressionType ~= self.VVOID then
			self:exp2NextReg(cc.valueExpression)
		end
		self:setList(cc.tableExpression.expValue.opcodeInfo, cc.recordElementCount, cc.pendingStoreCount)
	end
end

--- @param from number
--- @param n number
--- @return void
function FunctionState:processNil(from, n)
	local l = from + n - 1
	if self.pc > self.lastTarget and self.pc > 0 then
		local previousCode = self.f.code[self.pc]
		if self:getOpCode(previousCode) == self.OP_LOADNIL then
			local pFrom = self:getArgA(previousCode)
			local pl = pFrom + self:getArgB(previousCode)
			if (pFrom <= from and from <= pl + 1) or (from <= pFrom and pFrom <= l + 1) then
				if pFrom < from then
					from = pFrom
				end
				if pl > l then
					l = pl
				end
				local previous = InstructionPointer:new(self.f.code, self.pc - 1)
				self:setArgA(previous, from)
				self:setArgB(previous, l - from)
				return
			end
		end
	end
	self:codeABC(self.OP_LOADNIL, from, n - 1, 0)
end

--- @return number
function FunctionState:jump()
	local jpc = self.jpc.i
	self.jpc.i = self.NO_JUMP
	local j = IntPointer:new(self:codeAsBx(self.OP_JMP, 0, self.NO_JUMP))
	self:concat(j, jpc)
	return j.i
end

--- @param first number
--- @param nRet number
--- @return void
function FunctionState:ret(first, nRet)
	self:codeABC(self.OP_RETURN, first, nRet + 1, 0)
end

--- @param op number
--- @param a number
--- @param b number
--- @param c number
--- @return number
function FunctionState:condJump(op, a, b, c)
	self:codeABC(op, a, b, c)
	return self:jump()
end

--- @param pc number
--- @param dest number
--- @return void
function FunctionState:fixJump(pc, dest)
	local jmp = InstructionPointer:new(self.f.code, pc)
	local offset = dest - pc - 1
	self:assert(dest ~= self.NO_JUMP)
	if math.abs(offset) > self.MAXARG_sBx then
		self.ls:syntaxError('control structure too long')
	end
	self:setArgSbx(jmp, offset)
end

--- @return number
function FunctionState:getLabel()
	self.lastTarget = self.pc
	return self.pc
end

--- @param pc number
--- @return number
function FunctionState:getJump(pc)
	local offset = self:getArgSbx(self.f.code[pc + 1])
	if offset == self.NO_JUMP then
		return self.NO_JUMP
	else
		return pc + 1 + offset
	end
end

--- @param pc number
--- @return InstructionPointer
function FunctionState:getJumpControl(pc)
	local pi = InstructionPointer:new(self.f.code, pc)
	if pc >= 1 and self:testTMode(self:getOpCode(pi.code[pi.idx])) then
		return InstructionPointer:new(pi.code, pi.idx - 1)
	else
		return pi
	end
end

--- @param list number
--- @return boolean
function FunctionState:needValue(list)
	while list ~= self.NO_JUMP do
		local i = self:getJumpControl(list):get()
		if self:getOpCode(i) ~= self.OP_TESTSET then
			return true
		end
		list = self:getJump(list)
	end
	return false
end

--- @param node number
--- @param reg number
--- @return boolean
function FunctionState:patchTestReg(node, reg)
	local i = self:getJumpControl(node)
	if self:getOpCode(i:get()) ~= self.OP_TESTSET then
		return false
	end
	if reg ~= self.NO_REG and reg ~= self:getArgB(i:get()) then
		self:setArgA(i, reg)
	else
		i:set(self:createABC(self.OP_TEST, self:getArgB(i:get()), 0, self:getArgC(i:get())))
	end
	return true
end

--- @param list number
--- @return void
function FunctionState:removeValues(list)
	while list ~= self.NO_JUMP do
		self:patchTestReg(list, self.NO_REG)
		list = self:getJump(list)
	end
end

--- @param list number
--- @param vTarget number
--- @param reg number
--- @param dTarget number
--- @return void
function FunctionState:patchListAux(list, vTarget, reg, dTarget)
	while list ~= self.NO_JUMP do
		local next = self:getJump(list)
		if self:patchTestReg(list, reg) then
			self:fixJump(list, vTarget)
		else
			self:fixJump(list, dTarget)
		end
		list = next
	end
end

--- @return void
function FunctionState:disChargeJPc()
	self:patchListAux(self.jpc.i, self.pc, self.NO_REG, self.pc)
	self.jpc.i = self.NO_JUMP
end

--- @param list number
--- @param target number
--- @return void
function FunctionState:patchList(list, target)
	if target == self.pc then
		self:patchToHere(list)
	else
		self:assert(target < self.pc)
		self:patchListAux(list, target, self.NO_REG, target)
	end
end

--- @param list number
--- @param level number
--- @return void
function FunctionState:patchCloseUpvalues(list, level)
	level = level + 1
	local code = self.f.code
	while list ~= self.NO_JUMP do
		local next = self:getJump(list)
		local i = code[list + 1]
		self:assert(self:getOpCode(i) == self.OP_JMP and (self:getArgA(i) == 0 or self:getArgA(i) >= level))
		code[list + 1] = (i & (self.MASK_NOT_A)) | ((level << self.POS_A) & self.MASK_A)
		list = next
	end
end

--- @param list number
--- @return void
function FunctionState:patchToHere(list)
	self:getLabel()
	self:concat(self.jpc, list)
end

--- @param l1 number
--- @param l2 number
--- @return void
function FunctionState:concat(l1, l2)
	if l2 == self.NO_JUMP then
		return
	end
	if l1.i == self.NO_JUMP then
		l1.i = l2
	else
		local list = l1.i
		local next = self:getJump(list)
		while next ~= self.NO_JUMP do
			list = next
			next = self:getJump(list)
		end
		self:fixJump(list, l2)
	end
end

--- @param n number
--- @return void
function FunctionState:checkStack(n)
	local newStack = self.freeReg + n
	if newStack > self.f.maxStackSize then
		if newStack >= self.MAXSTACK then
			self.ls:syntaxError('function or expression too complex')
		end
		self.f.maxStackSize = newStack
	end
end

--- @param n number
--- @return void
function FunctionState:reserveRegs(n)
	self:checkStack(n)
	self.freeReg = self.freeReg + n
end

--- @param reg number
--- @return void
function FunctionState:setFreeReg(reg)
	if not self:isk(reg) and reg >= self.nActVar then
		self.freeReg = self.freeReg - 1
		self:assert(reg == self.freeReg)
	end
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:freeExp(e)
	if e.expressionType == self.VNONRELOC then
		self:setFreeReg(e.expValue.opcodeInfo)
	end
end

--- @param v LuaValue
--- @return number
function FunctionState:addK(v)
	local h = self.h
	if h == nil then
		h = {}
		self.h = h
	elseif h[v] then
		return h[v]
	end
	local idx = self.nk
	h[v] = idx
	--- @type LuaValue[]
	local k = self.f.k
	if k == nil then
		k = {}
		self.f.k = k
	end
	self.nk = idx + 1
	k[self.nk] = v
	return idx
end

--- @param s LuaString
--- @return number
function FunctionState:stringK(s)
	return self:addK(s)
end

--- @param r LuaValue
--- @return number
function FunctionState:numberK(r)
	return self:addK(r)
end

--- @param b boolean
--- @return number
function FunctionState:boolK(b)
	return self:addK(b and LuaBoolean._TRUE or LuaBoolean._FALSE)
end

--- @return number
function FunctionState:nilK()
	return self:addK(LuaNil._NIL)
end

--- @param e ExpressionDescriptor
--- @param nResults number
--- @return void
function FunctionState:setReturns(e, nResults)
	if e.expressionType == self.VCALL then
		self:setArgC(self:getCodePtr(e), nResults + 1)
	elseif e.expressionType == self.VVARARG then
		self:setArgB(self:getCodePtr(e), nResults + 1)
		self:setArgA(self:getCodePtr(e), self.freeReg)
		self:reserveRegs(1)
	end
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:setOneRet(e)
	if e.expressionType == self.VCALL then
		e.expressionType = self.VNONRELOC
		e.expValue.opcodeInfo = self:getArgA(self:getCode(e))
	elseif e.expressionType == self.VVARARG then
		self:setArgB(self:getCodePtr(e), 2)
		e.expressionType = self.VRELOCABLE
	end
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:disChargeVars(e)
	local k = e.expressionType
	if k == self.VLOCAL then
		e.expressionType = self.VNONRELOC
	elseif k == self.VUPVAL then
		e.expValue.opcodeInfo = self:codeABC(self.OP_GETUPVAL, 0, e.expValue.opcodeInfo, 0)
		e.expressionType = self.VRELOCABLE
	elseif k == self.VINDEXED then
		local op = self.OP_GETTABUP
		self:setFreeReg(e.expValue.rkIdx)
		if e.expValue.valueTypeIndex == self.VLOCAL then
			self:setFreeReg(e.expValue.tIdx)
			op = self.OP_GETTABLE
		end
		e.expValue.opcodeInfo = self:codeABC(op, 0, e.expValue.tIdx, e.expValue.rkIdx)
		e.expressionType = self.VRELOCABLE
	elseif k == self.VVARARG or k == self.VCALL then
		self:setOneRet(e)
	end
end

--- @param a number
--- @param b number
--- @param jump number
--- @return number
function FunctionState:codeLabel(a, b, jump)
	self:getLabel()
	return self:codeABC(self.OP_LOADBOOL, a, b, jump)
end

--- @param e ExpressionDescriptor
--- @param reg number
--- @return void
function FunctionState:disCharge2Reg(e, reg)
	self:disChargeVars(e)
	local k = e.expressionType
	if k == self.VNIL then
		self:processNil(reg, 1)
	elseif k == self.VFALSE or k == self.VTRUE then
		self:codeABC(self.OP_LOADBOOL, reg, e.expressionType == self.VTRUE and 1 or 0, 0)
	elseif k == self.VK then
		self:codeABx(self.OP_LOADK, reg, e.expValue.opcodeInfo)
	elseif k == self.VKNUM then
		self:codeABx(self.OP_LOADK, reg, self:numberK(e.expValue:getNumericValue()))
	elseif k == self.VRELOCABLE then
		local pc = self:getCodePtr(e)
		self:setArgA(pc, reg)
	elseif k == self.VNONRELOC then
		if reg ~= e.expValue.opcodeInfo then
			self:codeABC(self.OP_MOVE, reg, e.expValue.opcodeInfo, 0)
		end
	else
		self:assert(e.expressionType == self.VVOID or e.expressionType == self.VJMP)
		return
	end
	e.expValue.opcodeInfo = reg
	e.expressionType = self.VNONRELOC
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:disCharge2AnyReg(e)
	if e.expressionType ~= self.VNONRELOC then
		self:reserveRegs(1)
		self:disCharge2Reg(e, self.freeReg - 1)
	end
end

--- @param e ExpressionDescriptor
--- @param reg number
--- @return void
function FunctionState:exp2Reg(e, reg)
	self:disCharge2Reg(e, reg)
	if e.expressionType == self.VJMP then
		self:concat(e.trueJumpTarget, e.expValue.opcodeInfo)
	end
	if e:hasJumps() then
		local pF = self.NO_JUMP
		local pT = self.NO_JUMP
		if self:needValue(e.trueJumpTarget.i) or self:needValue(e.falseJumpTarget.i) then
			local fJ = e.expressionType == self.VJMP and self.NO_JUMP or self:jump()
			pF = self:codeLabel(reg, 0, 1)
			pT = self:codeLabel(reg, 1, 0)
			self:patchToHere(fJ)
		end
		local final = self:getLabel()
		self:patchListAux(e.falseJumpTarget.i, final, reg, pF)
		self:patchListAux(e.trueJumpTarget.i, final, reg, pT)
	end
	e.falseJumpTarget.i = self.NO_JUMP
	e.trueJumpTarget.i = self.NO_JUMP
	e.expValue.opcodeInfo = reg
	e.expressionType = self.VNONRELOC
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:exp2NextReg(e)
	self:disChargeVars(e)
	self:freeExp(e)
	self:reserveRegs(1)
	self:exp2Reg(e, self.freeReg - 1)
end

--- @param e ExpressionDescriptor
--- @return number
function FunctionState:exp2AnyReg(e)
	self:disChargeVars(e)
	if e.expressionType == self.VNONRELOC then
		if not e:hasJumps() then
			return e.expValue.opcodeInfo
		end
		if e.expValue.opcodeInfo >= self.nActVar then
			self:exp2Reg(e, e.expValue.opcodeInfo)
			return e.expValue.opcodeInfo
		end
	end
	self:exp2NextReg(e)
	return e.expValue.opcodeInfo
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:exp2AnyRegUp(e)
	if e.expressionType ~= self.VUPVAL or e:hasJumps() then
		self:exp2AnyReg(e)
	end
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:exp2Val(e)
	if e:hasJumps() then
		self:exp2AnyReg(e)
	else
		self:disChargeVars(e)
	end
end

--- @param e ExpressionDescriptor
--- @return number
function FunctionState:exp2RK(e)
	self:exp2Val(e)
	local k = e.expressionType
	if k == self.VTRUE or k == self.VFALSE or k == self.VNIL then
		if self.nk <= self.MAXINDEXRK then
			e.expValue.opcodeInfo = e.expressionType == self.VNIL and self:nilK() or self:boolK(e.expressionType == self.VTRUE)
			e.expressionType = self.VK
			return self:rkAsK(e.expValue.opcodeInfo)
		end
	elseif k == self.VKNUM then
		e.expValue.opcodeInfo = self:numberK(e.expValue:getNumericValue())
		e.expressionType = self.VK
		if e.expValue.opcodeInfo <= self.MAXINDEXRK then
			return self:rkAsK(e.expValue.opcodeInfo)
		end
	elseif k == self.VK then
		if e.expValue.opcodeInfo <= self.MAXINDEXRK then
			return self:rkAsK(e.expValue.opcodeInfo)
		end
	end
	return self:exp2AnyReg(e)
end

--- @param var ExpressionDescriptor
--- @param ex ExpressionDescriptor
--- @return void
function FunctionState:storeVar(var, ex)
	local k = var.expressionType
	if k == self.VLOCAL then
		self:freeExp(ex)
		self:exp2Reg(ex, var.expValue.opcodeInfo)
		return
	elseif k == self.VUPVAL then
		local e = self:exp2AnyReg(ex)
		self:codeABC(self.OP_SETUPVAL, e, var.expValue.opcodeInfo, 0)
	elseif k == self.VINDEXED then
		local op = (var.expValue.valueTypeIndex == self.VLOCAL) and self.OP_SETTABLE or self.OP_SETTABUP
		local e = self:exp2RK(ex)
		self:codeABC(op, var.expValue.tIdx, var.expValue.rkIdx, e)
	else
		self:assert(false)
	end
	self:freeExp(ex)
end

--- @param e ExpressionDescriptor
--- @param key ExpressionDescriptor
--- @return void
function FunctionState:processSelf(e, key)
	self:exp2AnyReg(e)
	self:freeExp(e)
	local func = self.freeReg
	self:reserveRegs(2)
	self:codeABC(self.OP_SELF, func, e.expValue.opcodeInfo, self:exp2RK(key))
	self:freeExp(key)
	e.expValue.opcodeInfo = func
	e.expressionType = self.VNONRELOC
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:invertJump(e)
	local pc = self:getJumpControl(e.expValue.opcodeInfo)
	self:assert(self:testTMode(self:getOpCode(pc:get())) and
			self:getOpCode(pc:get()) ~= self.OP_TESTSET and
			self:getOpCode(pc:get()) ~= self.OP_TEST)
	local a = self:getArgA(pc:get())
	local nota = a ~= 0 and 0 or 1
	self:setArgA(pc, nota)
end

--- @param e ExpressionDescriptor
--- @param cond number
--- @return number
function FunctionState:jumpOnCond(e, cond)
	if e.expressionType == self.VRELOCABLE then
		local ie = self:getCode(e)
		if self:getOpCode(ie) == self.OP_NOT then
			self.pc = self.pc - 1
			return self:condJump(self.OP_TEST, self:getArgB(ie), 0, cond ~= 0 and 0 or 1)
		end
	end
	self:disCharge2AnyReg(e)
	self:freeExp(e)
	return self:condJump(self.OP_TESTSET, self.NO_REG, e.expValue.opcodeInfo, cond)
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:goIfTrue(e)
	local pc
	self:disChargeVars(e)
	local k = e.expressionType
	if k == self.VJMP then
		self:invertJump(e)
		pc = e.expValue.opcodeInfo
	elseif k == self.VK or k == self.VKNUM or k == self.VTRUE then
		pc = self.NO_JUMP
	else
		pc = self:jumpOnCond(e, 0)
	end
	self:concat(e.falseJumpTarget, pc)
	self:patchToHere(e.trueJumpTarget.i)
	e.trueJumpTarget.i = self.NO_JUMP
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:goIfFalse(e)
	local pc
	self:disChargeVars(e)
	local k = e.expressionType
	if k == self.VJMP then
		pc = e.expValue.opcodeInfo
	elseif k == self.VNIL or k == self.VFALSE then
		pc = self.NO_JUMP
	else
		pc = self:jumpOnCond(e, 1)
	end
	self:concat(e.trueJumpTarget, pc)
	self:patchToHere(e.falseJumpTarget.i)
	e.falseJumpTarget.i = self.NO_JUMP
end

--- @param e ExpressionDescriptor
--- @return void
function FunctionState:codeNot(e)
	self:disChargeVars(e)
	local k = e.expressionType
	if k == self.VNIL or k == self.VFALSE then
		e.expressionType = self.VTRUE
	elseif k == self.VK or k == self.VKNUM or k == self.VTRUE then
		e.expressionType = self.VFALSE
	elseif k == self.VJMP then
		self:invertJump(e)
	elseif k == self.VRELOCABLE or k == self.VNONRELOC then
		self:disCharge2AnyReg(e)
		self:freeExp(e)
		e.expValue.opcodeInfo = self:codeABC(self.OP_NOT, 0, e.expValue.opcodeInfo, 0)
		e.expressionType = self.VRELOCABLE
	else
		self:assert(false)
	end
	do
		local temp = e.falseJumpTarget.i
		e.falseJumpTarget.i = e.trueJumpTarget.i
		e.trueJumpTarget.i = temp
	end
	self:removeValues(e.falseJumpTarget.i)
	self:removeValues(e.trueJumpTarget.i)
end

--- @param k number
--- @return boolean
function FunctionState:vkIsInReg(k)
	return k == self.VNONRELOC or k == self.VLOCAL
end

--- @param t ExpressionDescriptor
--- @param k ExpressionDescriptor
--- @return void
function FunctionState:indexed(t, k)
	t.expValue.tIdx = t.expValue.opcodeInfo
	t.expValue.rkIdx = self:exp2RK(k)
	self:assert(t.expressionType == self.VUPVAL or self:vkIsInReg(t.expressionType))
	t.expValue.valueTypeIndex = t.expressionType == self.VUPVAL and self.VUPVAL or self.VLOCAL
	t.expressionType = self.VINDEXED
end

--- @param op number
--- @param e1 ExpressionDescriptor
--- @param e2 ExpressionDescriptor
--- @return boolean
function FunctionState:constFolding(op, e1, e2)
	--- @type LuaValue
	local v1, v2, r
	if not e1:isNumeral() or not e2:isNumeral() then
		return false
	end
	if (op == self.OP_DIV or op == self.OP_MOD) and e2.expValue:getNumericValue():eq_b(LuaNumber.ZERO) then
		return false
	end
	v1 = e1.expValue:getNumericValue()
	v2 = e2.expValue:getNumericValue()
	if op == self.OP_ADD then
		r = v1:add(v2)
	elseif op == self.OP_SUB then
		r = v1:sub(v2)
	elseif op == self.OP_MUL then
		r = v1:mul(v2)
	elseif op == self.OP_DIV then
		r = v1:div(v2)
	elseif op == self.OP_MOD then
		r = v1:mod(v2)
	elseif op == self.OP_POW then
		r = v1:pow(v2)
	elseif op == self.OP_IDIV then
		r = v1:iDiv(v2)
	elseif op == self.OP_BAND then
		r = v1:bAnd(v2)
	elseif op == self.OP_BOR then
		r = v1:bOr(v2)
	elseif op == self.OP_BXOR then
		r = v1:bXor(v2)
	elseif op == self.OP_SHL then
		r = v1:shl(v2)
	elseif op == self.OP_SHR then
		r = v1:shr(v2)
	elseif op == self.OP_UNM then
		r = v1:neg()
	elseif op == self.OP_LEN then
		return false
	elseif op == self.OP_BNOT then
		return false
	else
		self:assert(false)
		r = nil
	end
	e1.expValue:setNumericValue(r)
	return true
end

--- @param op number
--- @param e1 ExpressionDescriptor
--- @param e2 ExpressionDescriptor
--- @param line number
--- @return void
function FunctionState:codeArithmetic(op, e1, e2, line)
	if self:constFolding(op, e1, e2) then
		return
	end
	local o2 = (op ~= self.OP_UNM and op ~= self.OP_LEN and
			op ~= self.OP_BNOT) and self:exp2RK(e2) or 0
	local o1 = self:exp2RK(e1)
	if o1 > o2 then
		self:freeExp(e1)
		self:freeExp(e2)
	else
		self:freeExp(e2)
		self:freeExp(e1)
	end
	e1.expValue.opcodeInfo = self:codeABC(op, 0, o1, o2)
	e1.expressionType = self.VRELOCABLE
	self:fixLine(line)
end

--- @param op number
--- @param cond number
--- @param e1 ExpressionDescriptor
--- @param e2 ExpressionDescriptor
--- @return void
function FunctionState:codeComp(op, cond, e1, e2)
	local o1 = self:exp2RK(e1)
	local o2 = self:exp2RK(e2)
	self:freeExp(e2)
	self:freeExp(e1)
	if cond == 0 and op ~= self.OP_EQ then
		local temp
		temp = o1
		o1 = o2
		o2 = temp
		cond = 1
	end
	e1.expValue.opcodeInfo = self:condJump(op, cond, o1, o2)
	e1.expressionType = self.VJMP
end

--- @param op number
--- @param e ExpressionDescriptor
--- @param line number
--- @return void
function FunctionState:preFix(op, e, line)
	--- @type ExpressionDescriptor
	local e2 = ExpressionDescriptor:new()
	e2:initialize(self.VKNUM, 0)
	if op == self.OPR_MINUS then
		if e:isNumeral() then
			e.expValue:setNumericValue(e.expValue:getNumericValue():neg())
		else
			self:exp2AnyReg(e)
			self:codeArithmetic(self.OP_UNM, e, e2, line)
		end
	elseif op == self.OPR_NOT then
		self:codeNot(e)
	elseif op == self.OPR_LEN then
		self:exp2AnyReg(e)
		self:codeArithmetic(self.OP_LEN, e, e2, line)
	elseif op == self.OPR_BNOT then
		self:exp2AnyReg(e)
		self:codeArithmetic(self.OP_BNOT, e, e2, line)
	else
		self:assert(false)
	end
end

--- @param op number
--- @param v ExpressionDescriptor
--- @return void
function FunctionState:inFix(op, v)
	if op == self.OPR_AND then
		self:goIfTrue(v)
	elseif op == self.OPR_OR then
		self:goIfFalse(v)
	elseif op == self.OPR_CONCAT then
		self:exp2NextReg(v)
	elseif op == self.OPR_ADD or
			op == self.OPR_SUB or
			op == self.OPR_MUL or
			op == self.OPR_DIV or
			op == self.OPR_MOD or
			op == self.OPR_POW or
			op == self.OPR_IDIV or
			op == self.OPR_BAND or
			op == self.OPR_BOR or
			op == self.OPR_BXOR or
			op == self.OPR_SHL or
			op == self.OPR_SHR then
		if not v:isNumeral() then
			self:exp2RK(v)
		end
	else
		self:exp2RK(v)
	end
end

--- @param op number
--- @param e1 ExpressionDescriptor
--- @param e2 ExpressionDescriptor
--- @param line number
--- @return void
function FunctionState:posFix(op, e1, e2, line)
	if op == self.OPR_AND then
		self:assert(e1.trueJumpTarget.i == self.NO_JUMP)
		self:disChargeVars(e2)
		self:concat(e2.falseJumpTarget, e1.falseJumpTarget.i)
		e1:setValue(e2)
	elseif op == self.OPR_OR then
		self:assert(e1.falseJumpTarget.i == self.NO_JUMP)
		self:disChargeVars(e2)
		self:concat(e2.trueJumpTarget, e1.trueJumpTarget.i)
		e1:setValue(e2)
	elseif op == self.OPR_CONCAT then
		self:exp2Val(e2)
		if e2.expressionType == self.VRELOCABLE and
				self:getOpCode(self:getCode(e2)) == self.OP_CONCAT then
			self:assert(e1.expValue.opcodeInfo == self:getArgB(self:getCode(e2)) - 1)
			self:freeExp(e1)
			self:setArgB(self:getCodePtr(e2), e1.expValue.opcodeInfo)
			e1.expressionType = self.VRELOCABLE
			e1.expValue.opcodeInfo = e2.expValue.opcodeInfo
		else
			self:exp2NextReg(e2)
			self:codeArithmetic(self.OP_CONCAT, e1, e2, line)
		end
	elseif op == self.OPR_ADD then
		self:codeArithmetic(self.OP_ADD, e1, e2, line)
	elseif op == self.OPR_SUB then
		self:codeArithmetic(self.OP_SUB, e1, e2, line)
	elseif op == self.OPR_MUL then
		self:codeArithmetic(self.OP_MUL, e1, e2, line)
	elseif op == self.OPR_DIV then
		self:codeArithmetic(self.OP_DIV, e1, e2, line)
	elseif op == self.OPR_MOD then
		self:codeArithmetic(self.OP_MOD, e1, e2, line)
	elseif op == self.OPR_POW then
		self:codeArithmetic(self.OP_POW, e1, e2, line)
	elseif op == self.OPR_IDIV then
		self:codeArithmetic(self.OP_IDIV, e1, e2, line)
	elseif op == self.OPR_BAND then
		self:codeArithmetic(self.OP_BAND, e1, e2, line)
	elseif op == self.OPR_BOR then
		self:codeArithmetic(self.OP_BOR, e1, e2, line)
	elseif op == self.OPR_BXOR then
		self:codeArithmetic(self.OP_BXOR, e1, e2, line)
	elseif op == self.OPR_SHL then
		self:codeArithmetic(self.OP_SHL, e1, e2, line)
	elseif op == self.OPR_SHR then
		self:codeArithmetic(self.OP_SHR, e1, e2, line)
	elseif op == self.OPR_EQ then
		self:codeComp(self.OP_EQ, 1, e1, e2)
	elseif op == self.OPR_NE then
		self:codeComp(self.OP_EQ, 0, e1, e2)
	elseif op == self.OPR_LT then
		self:codeComp(self.OP_LT, 1, e1, e2)
	elseif op == self.OPR_LE then
		self:codeComp(self.OP_LE, 1, e1, e2)
	elseif op == self.OPR_GT then
		self:codeComp(self.OP_LT, 0, e1, e2)
	elseif op == self.OPR_GE then
		self:codeComp(self.OP_LE, 0, e1, e2)
	else
		self:assert(false)
	end
end

--- @param line number
--- @return void
function FunctionState:fixLine(line)
	self.f.lineInfo[self.pc] = line
end

--- @param instruction number
--- @param line number
--- @return number
function FunctionState:code(instruction, line)
	local f = self.f
	self:disChargeJPc()
	local pc = self.pc + 1
	local code = f.code
	local lineinfo = f.lineInfo
	if code == nil then
		code = {}
		f.code = code
	end
	if lineinfo == nil then
		lineinfo = {}
		f.lineInfo = lineinfo
	end
	code[pc] = instruction
	lineinfo[pc] = line
	self.pc = pc
	return pc - 1
end

--- @param o number
--- @param a number
--- @param b number
--- @param c number
--- @return number
function FunctionState:codeABC(o, a, b, c)
	self:assert(self:getOpMode(o) == self.iABC)
	self:assert(self:getBMode(o) ~= self.OpArgN or b == 0)
	self:assert(self:getCMode(o) ~= self.OpArgN or c == 0)
	return self:code(self:createABC(o, a, b, c), self.ls.lastLine)
end

--- @param o number
--- @param a number
--- @param bc number
--- @return number
function FunctionState:codeABx(o, a, bc)
	self:assert(self:getOpMode(o) == self.iABx or self:getOpMode(o) == self.iAsBx)
	self:assert(self:getCMode(o) == self.OpArgN)
	self:assert(bc >= 0 and bc <= self.MAXARG_Bx)
	return self:code(self:createAbx(o, a, bc), self.ls.lastLine)
end

--- @param base number
--- @param nElements number
--- @param toStore number
--- @return void
function FunctionState:setList(base, nElements, toStore)
	local c = math.floor((nElements - 1) / self.LFIELDS_PER_FLUSH) + 1
	local b = toStore == self.LUA_MULTRET and 0 or toStore
	self:assert(toStore ~= 0)
	if c <= self.MAXARG_C then
		self:codeABC(self.OP_SETLIST, base, b, c)
	else
		self:codeABC(self.OP_SETLIST, base, b, 0)
		self:code(c, self.ls.lastLine)
	end
	self.freeReg = base + 1
end

--- @param a number
--- @return void
function FunctionState:codeExtraArg(a)
	self:assert(a <= 0x03ffffff)
	self:code(self:createAx(39, a), self.ls.lastLine)
end

--- @param reg number
--- @param k number
--- @return number
function FunctionState:codeK(reg, k)
	if k <= 262143 then
		return self:codeABx(1, reg, k)
	end
	local codeABx = self:codeABx(2, reg, 0)
	self:codeExtraArg(k)
	return codeABx
end

return FunctionState